/*
 * Copyright (C) 2013 Manoel Afonso Filho
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package agimagem.concrete;

import agimagem.fitness.Fitness;
import agimagem.populacao.Individuo;
import agimagem.strategy.Mutacao;
import agimagem.util.AGChave;
import agimagem.util.Util;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 *
 * @author Manoel Afonso Filho
 */
public class MutacaoIV implements Mutacao {

    private static Logger log = LogManager.getLogger(MutacaoIV.class.getName());
    private SecureRandom sr;

    /**
     * Inicia o gerador de números aleatórios.
     */
    public MutacaoIV() {
        try {
            sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
        } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
            log.error("Erro ao obter instância do gerador de números aleatórios: {}", ex);
        }
        sr.nextBytes(new byte[]{});
    }

    @Override
    public Individuo executar(Individuo ind) {
        byte[] imagem = ind.getCiphertext();
        byte[][] blocos = Util.dividirArray(imagem, 8);

        //Sorteia o ponto de mutação (de 0 a 7).
        byte[] tmp1 = new byte[1];
        sr.nextBytes(tmp1);
        byte pm = (byte) (tmp1[0] % 8); //intervalo de 0 a 7.
        if (pm < 0) {
            pm = (byte) -pm;
        }

        //Sorteia o vetor de inicialização (-128 a 127).
        byte[] tmp2 = new byte[1];
        sr.nextBytes(tmp2);
        byte iv = tmp2[0];

        //Realiza a mutação
        for (int i = 0; i < blocos.length; i++) {
            byte[] pedaco = blocos[i];
            byte[] novoPedaco = new byte[pedaco.length];

            for (int j = 0; j < pedaco.length; j++) {
                novoPedaco[j] = (byte) (pedaco[(pm + j) % pedaco.length] ^ iv);
            }

            blocos[i] = novoPedaco;
        }

        //Concatena os blocos
        Individuo novoInd = new Individuo(Util.concatenarArrays(blocos));
        novoInd.setChaves(ind.getChaves());
        novoInd.getChaveDaGeracaoAtual().setMutacao(pm, iv);
        novoInd.setFitness(Fitness.calcular(novoInd));

        return novoInd;
    }

    @Override
    public byte[] reverso(byte[] ciphertext, AGChave chave) {
        //Ponto de mutação
        int pm = chave.getPontoMutacao();

        //Nem todos os indivíduos passam por mutação. Apenas os indivíduos
        //que foram selecionados para o crossover tem mutação.
        if(pm == -128){
            return ciphertext;
        }
        //Vetor de inicialização
        byte iv = chave.getByteOriginal();
        //Blocos
        byte[][] blocos = Util.dividirArray(ciphertext, 8);

        try {
            for (int i = 0; i < blocos.length; i++) {
                byte[] pedaco = blocos[i];
                byte[] novoPedaco = new byte[pedaco.length];
                for (int j = 0; j < pedaco.length; j++) {
                    novoPedaco[(j + pm) % 8] = (byte) (pedaco[j] ^ iv);
                }
                blocos[i] = novoPedaco;
            }

            ciphertext = Util.concatenarArrays(blocos);
        } catch (Exception ex) {
            log.error("Erro na decriptografia. Pm = {}, IV = {}. Erro: {}", pm, iv, ex);
            System.exit(-1);
        }

        return ciphertext;
    }
}
